<?xml version="1.0" encoding="utf-8-unix"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>附：USACO中的背包问题</title>
    <meta name="generator" content="muse.el" />
    <meta http-equiv="Content-Type"
          content="text/html; charset=utf-8" />
    <style type="text/css">
body {
  background: white; color: black;
  margin-left: 3%; margin-right: 7%;
}

p { margin-top: 1% }
p.verse { margin-left: 3% }

.example { margin-left: 3% }

h2 {
  margin-top: 25px;
  margin-bottom: 0px;
}
h3 { margin-bottom: 0px; }
    </style>
  </head>
  <body>
    <h1>附：USACO中的背包问题</h1>
    <!-- Page published by Emacs Muse begins here -->
<p><a href="http://www.usaco.org/">USACO</a>是USA Computing Olympiad的简称，它组织了很多面向全球的计算机竞赛活动。</p>

<p><a href="http://train.usaco.org/">USACO Trainng</a>是一个很适合初学者的题库，我认为它的特色是题目质量高，循序渐进，还配有不错的课文和题目分析。其中关于背包问题的那篇课文 (TEXT Knapsack Problems) 也值得一看。</p>

<p>另外，<a href="http://contest.usaco.org/">USACO Contest</a>是USACO常年组织的面向全球的竞赛系列，在此也推荐NOIP选手参加。</p>

<p>我整理了USACO Training中涉及背包问题的题目，应该可以作为不错的习题。其中标加号的是我比较推荐的，标叹号的是我认为对NOIP选手比较有挑战性的。</p>

<h2>题目列表</h2>

<ul>
<li>Inflate (+) （基本01背包）</li>

<li>Stamps (+)(!) （对初学者有一定挑战性）</li>

<li>Money</li>

<li>Nuggets</li>

<li>Subsets</li>

<li>Rockers (+) （另一类有趣的“二维”背包问题）</li>

<li>Milk4 (!) （很怪的背包问题问法，较难用纯DP求解）</li>
</ul>


<h2>题目简解</h2>

<p class="first">以下文字来自我所撰的《USACO心得》一文，该文的完整版本，包括我的程序，可在<a href="http://my.opera.com/dd-usaco/">DD的USACO征程</a>中找到。</p>

<blockquote>
<p class="quoted"></p>
<p class="quoted">Inflate 是加权01 背包问题，也就是说：每种物品只有一件，只可以选择放或者
不放；而且每种物品有对应的权值，目标是使总权值最大或最小。它最朴素的状态
转移方程是：f[k][i] = max{f[k-1][i] , f[k-1][i-v[k]]+w[k]}。f[k][i]表示前k 件物品花费代
价i 可以得到的最大权值。v[k]和w[k]分别是第k 件物品的花费和权值。可以看到，
f[k]的求解过程就是使用第k 件物品对f[k-1]进行更新的过程。那么事实上就不用使用
二维数组，只需要定义f[i]，然后对于每件物品k，顺序地检查f[i]与f[i-v[k]]+w[k]的大
小，如果后者更大，就对前者进行更新。这是背包问题中典型的优化方法。</p>
<p class="quoted">题目stamps 中，每种物品的使用量没有直接限制，但使用物品的总量有限制。
求第一个不能用这有限个物品组成的背包的大小。（可以这样等价地认为）设f[k][i]
表示前k 件物品组成大小为i 的背包， 最少需要物品的数量。则f[k][i]=
min{f[k-1][i],f[k-1][i-j*s[k]]+j}，其中j 是选择使用第k 件物品的数目，这个方程运用时
可以用和上面一样的方法处理成一维的。求解时先设置一个粗糙的循环上限，即最
大的物品乘最多物品数。</p>
<p class="quoted">Money 是多重背包问题。也就是每个物品可以使用无限多次。要求解的是构成
一种背包的不同方案总数。基本上就是把一般的多重背包的方程中的min 改成sum
就行了。</p>
<p class="quoted">Nuggets 的模型也是多重背包。要求求解所给的物品不能恰好放入的背包大小
的最大值（可能不存在）。只需要根据“若i、j 互质，则关于x、y 的不定方程i*x+y*j=n
必有正整数解，其中n&gt;i*j”这一定理得出一个循环的上限。
Subsets 子集和问题相当于物品大小是前N 个自然数时求大小为N*(N+1)/4 的
01 背包的方案数。</p>
<p class="quoted">Rockers 可以利用求解背包问题的思想设计解法。我的状态转移方程如下：
f[i][j][t]=max{f[i][j][t-1] , f[i-1][j][t] , f[i-1][j][t-time[i]]+1 , f[i-1][j-1][T]+(t&gt;=time[i])}。其中
f[i][j][t]表示前i 首歌用j 张完整的盘和一张录了t 分钟的盘可以放入的最多歌数，T 是
一张光盘的最大容量，t&gt;=time[i]是一个bool 值转换成int 取值为0 或1。但我后来发
现我当时设计的状态和方程效率有点低，如果换成这样：f[i][j]=(a,b)表示前i 首歌中
选了j 首需要用到a 张完整的光盘以及一张录了b 分钟的光盘，会将时空复杂度都大
大降低。这种将状态的值设为二维的方法值得注意。</p>
<p class="quoted">Milk4 是这些类背包问题中难度最大的一道了。很多人无法做到将它用纯DP 方
法求解，而是用迭代加深搜索枚举使用的桶，将其转换成多重背包问题再DP。由于
USACO 的数据弱，迭代加深的深度很小，这样也可以AC，但我们还是可以用纯DP
方法将它完美解决的。设f[k]为称量出k 单位牛奶需要的最少的桶数。那么可以用类
似多重背包的方法对f 数组反复更新以求得最小值。然而困难在于如何输出字典序最
小的方案。我们可以对每个i 记录pre_f[i]和pre_v[i]。表示得到i 单位牛奶的过程是
用pre_f[i]单位牛奶加上若干个编号为pre_v[i]的桶的牛奶。这样就可以一步步求得得
到i 单位牛奶的完整方案。为了使方案的字典序最小，我们在每次找到一个耗费桶数
相同的方案时对已储存的方案和新方案进行比较再决定是否更新方案。为了使这种
比较快捷，在使用各种大小的桶对f 数组进行更新时先大后小地进行。USACO 的官
方题解正是这一思路。如果认为以上文字比较难理解可以阅读官方程序或我的程序。</p>


</blockquote>

<p><a href="Index.html">首页</a></p>

<hr />

<p>Copyright (c)  2007  Tianyi Cui</p>

<p>Permission is granted to copy, distribute and/or modify this document under the terms of the <a href="http://www.gnu.org/licenses/fdl.txt">GNU Free Documentation License</a>, Version 1.2 or any later version published by the Free Software Foundation.</p>



<!-- Page published by Emacs Muse ends here -->
  </body>
</html>
